#include "ustandard/ustandard_sys.h"
#include "ustandard/udevelop.h"
#include "ustandard/uconf-parse.h"
#define LEN_LINE                    1024
#define MAX_CATEGORY                100
#define MAX_ITEM_IN_CATEGORY        100

#ifndef ustd_error
    #define ustd_error printf
#endif

int ksize = 0;


//#include "src/uconf/ustring.inc.c"
//#include "ustring.inc.c"
//#include "ufile.inc.c"


struct conf_item {
    char* name;
    char* value;
};


struct conf_category {
    char* name;
    int num_conf_items;
    struct conf_item items[MAX_ITEM_IN_CATEGORY];
};


#define LEN_PATH    1024
struct conf {
    char path[LEN_PATH];
    int num_conf_category;
    struct conf_category category[MAX_CATEGORY];
};


/*
    0. not recognized.
    1. category.
    2. item.
    3. comment.
    -1 : ENOMEM.
*/
int _uconf_parse_line(char* line, struct conf_item* item, char** category);
int _uconf_add_category(CONF* conf, char* name_category);
int _uconf_add_item(CONF* conf, struct conf_item* item);
int _uconf_parse(CONF* conf);


CONF* uconf_open(const char* path, conf_mode_e mode)
{
    int ret =0;

    CONF* conf = um_malloc(sizeof(CONF));
    if(NULL == conf) {
        return NULL;
    }
    memset(conf, 0, sizeof(CONF));
    strncpy(conf->path, path, LEN_PATH-1);

    ret = _uconf_parse(conf);

    return conf;
}


int _uconf_parse_line(char* line, struct conf_item* item, char** category)
{
    int ret = 0; 
    size_t len;
    char* tmp;

    __ustrtrim_chars(line, kchars_blank, sizeof(kchars_blank));
    len = strlen(line);

    if(len >3 && line[0] == '[' && line[len-1] == ']') {
        ret = 1;
        int len_category = len -2 ;
        *category = __ustrndup(line+1, len_category);
        if(NULL == *category) {
            ret = -1;
            goto finish;
        }
        __ustrtrim_chars(*category, kchars_blank, sizeof(kchars_blank));
    }
    else if(len > 3 && NULL != (tmp = strchr(line, '=')) && tmp > line && tmp < line+(len-1)) {
        ret = 2;
        int len_name = tmp - line;
        int len_value = line + (len-1) - tmp;
        item->name = __ustrndup(line, len_name);
        if(NULL == item->name) {
            ret = -1;
            goto finish;
        }
        __ustrtrim_chars(item->name, kchars_blank, sizeof(kchars_blank));

        item->value = __ustrndup(tmp+1, len_value);
        if(NULL == item->value) {
            _free(item->name, 1);
            item->name = NULL;
            ret = -1;
            goto finish;
        }
        __ustrtrim_chars(item->value, kchars_blank, sizeof(kchars_blank));
    }
    else if(line[0] == '#') {
        ret = 3;
    }

finish:
    return ret;
}


int _uconf_add_category(CONF* conf, char* name_category)
{
    int ret = 0;

    if(conf->num_conf_category < MAX_CATEGORY) {
        conf->category[conf->num_conf_category].name = name_category;
        conf->num_conf_category ++ ;
    }
    else {
        ustd_error("%s : category full.\n", "conf");
        ret = -1;
    }
    
    return ret;
}


int _uconf_add_item(CONF* conf, struct conf_item* item)
{
    int ret = 0;

    if(conf->num_conf_category == 0) {
        conf->category[0].name = __ustrndup("*", 1);
        if(NULL == conf->category[0].name) {
            ret = -1;
            return ret;
        }
        conf->num_conf_category ++ ;
    }

    struct conf_category* category = NULL;
    category = &conf->category[conf->num_conf_category-1];
    
    if(category->num_conf_items < MAX_ITEM_IN_CATEGORY) {
        category->items[category->num_conf_items] = *item;
        category->num_conf_items ++ ;
    }
    else {
        ustd_error("%s : category item full.\n", "conf");
        ret = -1;
    }

    return ret;
}


int _uconf_parse(CONF* conf)
{
    int ret = 0;

    char line[LEN_LINE];
    FILE* fp = fopen(conf->path, "r");
    if(NULL == fp) {
        ustd_error("uconf : config file read error.\n");
        return -1;
    }
    
    while(1) {
        /* read line. */
        memset(line, 0, LEN_LINE);
        char* tmp = fgets(line, LEN_LINE, fp);
        if(NULL == tmp) {
            break;
        }

        /* parse line. */
        struct conf_item tmp_item;
        char* name_category = NULL;
        int ret_parse = _uconf_parse_line(line, &tmp_item, &name_category); 
        if(1 == ret_parse) {
            _uconf_add_category(conf, name_category);
        }
        else if(2 == ret_parse) {
            _uconf_add_item(conf, &tmp_item);
        }

        /* skip tailer \r\n. */
        while(*tmp != '\0' && (*tmp == '\r' || *tmp == '\n')) {
            tmp ++ ;
        }
    }

    fclose(fp);

    return ret;
}


int uconf_get_category_num(CONF* conf)
{
    int num = conf->num_conf_category;
    return num;
}


const char* uconf_get_category(CONF* conf, int index_category)
{
    const char* name;
    if(index_category >= 0 && index_category < conf->num_conf_category) {
        name = conf->category[index_category].name;
    }
    else {
        name = NULL;
    }

    return name;
}


/*
    Input name_category to NULL or "*" means wildcard any category.
*/
const char* uconf_get_string(CONF* conf, const char* name_category, const char* name_item)
{
    const char* value = NULL;

    int i, j;
    struct conf_category* category = NULL;
    struct conf_item* item;

    int category_wildcard = 0;
    if(NULL == name_category || 0 == strcmp(name_category, "*")) {
        category_wildcard = 1;
    }

    for(i=0; i<conf->num_conf_category && NULL == value; i++) {
        category = &conf->category[i];
        if(0 == category_wildcard 
                && (NULL == category->name || 0 != strcmp(category->name, name_category))) {
            continue;
        }

        for(j=0; j<category->num_conf_items; j++) {
            item = &category->items[j];
            if(NULL != item->name && 0 == strcmp(item->name, name_item)) {
                value = item->value;
                break;
            }
        }
    }

    return value;
}


int uconf_close(CONF* conf)
{
    int ret = 0;

    int i, j;
    struct conf_category* category = NULL;
    struct conf_item* item;
    for(i=0; i<conf->num_conf_category; i++) {
        category = &conf->category[i];
        for(j=0; j<category->num_conf_items; j++) {
            item = &category->items[j];
            if(NULL != item->name) {
                um_free(item->name);
                item->name = NULL;
            }

            if(NULL != item->value) {
                um_free(item->value);
                item->value = NULL;
            }
        }

        if(NULL != category->name) {
            um_free(category->name);
            category->name = NULL;
        }
    }

    um_free(conf);
    conf = NULL;

    return ret;
}


int uconf_read_category_num(CONF* conf)
{
    int retn = 0;
    retn = conf->num_conf_category;
    return retn;
}


const char* uconf_read_category(CONF* conf, int idx_category)
{
    const char* retstr = NULL;
    if(idx_category>=0 && idx_category<conf->num_conf_category){
        retstr = conf->category[idx_category].name;
    }

    return retstr;
}


int uconf_read_itemvalue_num(CONF* conf, int idx_category)
{
    int retn = 0;
    if(idx_category>=0 && idx_category<conf->num_conf_category){
        retn = conf->category[idx_category].num_conf_items;
    }

    return retn;
}


struct conf_item* uconf_read_itemvalue(CONF* conf, 
        int idx_category, int idx_item)
{
    struct conf_item* ret_item = NULL;
    int num_items = uconf_read_itemvalue_num(conf, idx_category);
    if(num_items >0 && (idx_item>=0 && idx_item<num_items)) {
        ret_item = &(conf->category[idx_category].items[idx_item]);
    }

    return ret_item;
}


const char* uconf_read_value(CONF* conf, const char* category, const char* item)
{


    return NULL;
}


int uconf_add(const char* path, 
        const char* name_category, const char* name_item, const char* value)
{
    int ret = 0;

    char line[LEN_LINE];
    FILE* fp = fopen(path, "r");
    if(NULL == fp) {
        ustd_error("uconf : config file read error.\n");
        ret = -1;
        goto finished;
    }

    long offset = 0;
    
    while(1) {
        /* read line. */
        memset(line, 0, LEN_LINE);
        char* tmp = fgets(line, LEN_LINE, fp);
        if(NULL == tmp) {
            break;
        }

        /* parse line. */
        struct conf_item tmp_item;
        char* tmp_category = NULL;
        int ret_parse = _uconf_parse_line(line, &tmp_item, &tmp_category); 
        if(1 == ret_parse) {
            if(0 == strcmp(name_category, tmp_category)) {
                offset = ftell(fp);
                um_free(tmp_category);
                break;
            }

            um_free(tmp_category);
        }
        else if(2 == ret_parse) {
            um_free(tmp_item.name);
            um_free(tmp_item.value);
        }
    }

    fclose(fp);

    snprintf(line, LEN_LINE, "[%s]\n%s : %s\n", name_category, name_item, value);   
    ufile_insert(path, offset, line, 1, strlen(line));

finished:
    return ret;
}


int uconf_update(const char* path, 
        const char* name_category, const char* name_item, const char* value)
{
    int ret = 0;

    char line[LEN_LINE];
    FILE* fp = fopen(path, "r");
    if(NULL == fp) {
        ustd_error("uconf : config file read error.\n");
        ret = -1;
        goto finished;
    }

    long offset = 0;
    int is_category_found = 0;
    int is_item_found = 0;
    size_t size_line = 0;
    
    while(1) {
        /* read line. */
        memset(line, 0, LEN_LINE);
        char* tmp = fgets(line, LEN_LINE, fp);
        if(NULL == tmp) {
            break;
        }
        size_line = strlen(line); 

        /* parse line. */
        struct conf_item tmp_item;
        char* tmp_category = NULL;
        int ret_parse = _uconf_parse_line(line, &tmp_item, &tmp_category); 
        if(1 == ret_parse) {
            /* category found, but item not found. 
               add the item to the category bottom.*/
            if(is_category_found) {
                offset = ftell(fp) - size_line;
                um_free(tmp_category);
                break;
            }
            else if(0 == strcmp(name_category, tmp_category)) {
                is_category_found = 1;
            }

            um_free(tmp_category);
        }
        else if(2 == ret_parse) {
                /* category and item both found. */
            if(is_category_found 
                    && 0 == strcmp(name_item, tmp_item.name)) {
                is_item_found = 1; 
                offset = ftell(fp) - size_line;

                um_free(tmp_item.name);
                um_free(tmp_item.value);

                break;
            }

            um_free(tmp_item.name);
            um_free(tmp_item.value);
        }
    }
    fclose(fp);

    if(is_category_found) {
        if(is_item_found) {
            snprintf(line, LEN_LINE, "%s : %s\n", name_item, value);   
            ufile_replace(path, offset, size_line, line, 1, strlen(line));
        }
        else {
            snprintf(line, LEN_LINE, "%s : %s\n", name_item, value);   
            ufile_insert(path, offset, line, 1, strlen(line));
        }
    }
    else {
        snprintf(line, LEN_LINE, "[%s]\n%s : %s\n", 
                name_category, name_item, value);   
        ufile_insert(path, offset, line, 1, strlen(line));
    }

finished:
    return ret;
}
